/**
 * Copyright (C) 2010 Cloudfarming <info@cloudfarming.nl>
 *
 * Licensed under the Eclipse Public License - v 1.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *         http://www.eclipse.org/legal/epl-v10.html
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package nl.cloudfarming.client.geoviewer.jxmap.map;

import com.vividsolutions.jts.geom.Point;
import java.awt.EventQueue;
import java.awt.dnd.DnDConstants;
import java.awt.dnd.DropTarget;
import java.awt.dnd.DropTargetDragEvent;
import java.awt.dnd.DropTargetDropEvent;
import java.awt.dnd.DropTargetEvent;
import java.awt.dnd.DropTargetListener;
import java.beans.IntrospectionException;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.beans.PropertyVetoException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.swing.ActionMap;
import javax.swing.JPanel;
import javax.swing.text.DefaultEditorKit;
import nl.cloudfarming.client.geoviewer.BaseLayer;
import nl.cloudfarming.client.geoviewer.Layer;
import nl.cloudfarming.client.geoviewer.events.LayerEventKey;
import nl.cloudfarming.client.geoviewer.LayerInfo;
import nl.cloudfarming.client.geoviewer.LayerNode;
import nl.cloudfarming.client.geoviewer.LayerNodeChildFactory;
import nl.cloudfarming.client.geoviewer.LayerObject;
import nl.cloudfarming.client.geoviewer.MapController;
import nl.cloudfarming.client.geoviewer.ObjectLayer;
import nl.cloudfarming.client.geoviewer.Preset;
import nl.cloudfarming.client.geoviewer.jxmap.layerlist.LayerController;
import nl.cloudfarming.client.geoviewer.jxmap.layerlist.LayerListChildFactory;
import nl.cloudfarming.client.geoviewer.jxmap.layerlist.LayerListLayerNode;
import nl.cloudfarming.client.geoviewer.jxmap.layerlist.LayerListPanel;
import nl.cloudfarming.client.geoviewer.jxmap.layerlist.LayerListRootNode;
import nl.cloudfarming.eventbus.GuiEvent;
import nl.cloudfarming.eventbus.GuiEventBus;
import nl.cloudfarming.eventbus.GuiEventKey;
import nl.cloudfarming.eventbus.GuiEventListener;
import org.jdesktop.swingx.JXMapViewer;
import org.jdesktop.swingx.mapviewer.GeoPosition;
import org.openide.awt.StatusDisplayer;
import org.openide.explorer.ExplorerManager;
import org.openide.explorer.ExplorerUtils;
import org.openide.nodes.Children;
import org.openide.nodes.Node;
import org.openide.nodes.NodeTransfer;
import org.openide.util.Exceptions;
import org.openide.util.Lookup;
import org.openide.util.lookup.ProxyLookup;

/**
 * Controller of the rootmap panel.
 * 
 *
 * @author Timon Veenstra
 */
public class RootMapPanelController implements MapController, Lookup.Provider, ExplorerManager.Provider {

    private final RootMapPanel rootMapPanel;
    private final LayerListPanel layerListPanel = new LayerListPanel();
    private final Map<Layer, LayerPanel> layerPanelMap = new HashMap<Layer, LayerPanel>();
    private boolean active = false;
    private final ExplorerManager layerListEM = new ExplorerManager();
    private Lookup lookup;
    private final LayerController layerController = new MapLayerController();
    private final MapPanelFactory mapPanelFactory;
    
    /**
     * DO NOT ADD LOOKUPS TO THIS LOOKUP MANUALLY BUT USE THE addLookup/removeLookup METHODS
     */
    private final List<Lookup> lookups = new ArrayList<Lookup>();

    public RootMapPanelController() {
        rootMapPanel = new RootMapPanel(layerListEM, layerListPanel);
        //
        // initialize the layers
        //
        initLayers();


        layerListEM.setRootContext(new LayerListRootNode(new LayerListChildFactory(layerController)));
        layerListEM.getRootContext().setDisplayName("");

//        ExplorerChangeListener changeListener = new ExplorerChangeListener();
//        manager.addPropertyChangeListener(changeListener);

        ActionMap map = new ActionMap();
        map.put(DefaultEditorKit.copyAction, ExplorerUtils.actionCopy(layerListEM));
        map.put(DefaultEditorKit.cutAction, ExplorerUtils.actionCut(layerListEM));
        map.put(DefaultEditorKit.pasteAction, ExplorerUtils.actionPaste(layerListEM));
        map.put("delete", ExplorerUtils.actionDelete(layerListEM, true)); // or false

        //
        // proxy layerlist lookup aswel as lookup from the layer data manager to enable save cookie
        //
        addLookup(ExplorerUtils.createLookup(layerListEM, map));
        //
        // register property change listener to handle change in node selection.
        //
        GuiEventBus.addListener(new NewLayerListener());

        MapController.Registry.addMapController(this);

        //
        // init the layerlist
        //
        LayerDropTargetListener dtl = new LayerDropTargetListener();
        DropTarget dt = new DropTarget(layerListPanel, dtl);
        dt.setDefaultActions(DnDConstants.ACTION_COPY);
        dt.setActive(true);
        layerListPanel.setDropTarget(dt);


        mapPanelFactory = new MapPanelFactory(getViewer(), this);
    }
    
    
    private void addLookup(Lookup lookup){
        lookups.add(lookup);
        this.lookup = new ProxyLookup(lookups.toArray(new Lookup[lookups.size()]));
    }
    
    private void removeLookup(Lookup lookup){
        lookups.remove(lookup);
        this.lookup = new ProxyLookup(lookups.toArray(new Lookup[lookups.size()]));
    }    

    private void initLayers() {
        //TODO retrieve layers from properties
    }

    public JPanel getPanel() {
        return this.rootMapPanel;
    }

    private void repaintLayer(final Layer layer) {
        if (EventQueue.isDispatchThread()) {
            if (layerPanelMap.containsKey(layer)) {
                layerPanelMap.get(layer).repaint();
            }
        } else {
            EventQueue.invokeLater(new Runnable() {

                @Override
                public void run() {
                    if (layerPanelMap.containsKey(layer)) {
                        layerPanelMap.get(layer).repaint();
                    }

                }
            });
        }
    }


    private void refreshLayerList() {
        if (this.layerListEM.getRootContext() instanceof LayerListRootNode) {
            ((LayerListRootNode) this.layerListEM.getRootContext()).refresh();
        }
    }

    @Override
    public LayerInfo getLayerInfo(Layer layer) {
        assert layerPanelMap.containsKey(layer);
        return layerPanelMap.get(layer).getInfo();
    }

    @Override
    public Preset getCurrentPreset() {
        throw new UnsupportedOperationException("Not supported yet.");
    }

    @Override
    public String getMapName() {
        return "map:" + System.identityHashCode(this);
    }

    JXMapViewer getViewer() {
        return rootMapPanel.getMapViewer();
    }

    @Override
    public void zoomToObject(LayerObject layerObject, Layer layer) {
        if (!this.layerPanelMap.containsKey(layer)) {
            return;
        }

        Point centroid = layerObject.getGeometry().getCentroid();
        GeoPosition geoPosition = new GeoPosition(centroid.getY(), centroid.getX());
        rootMapPanel.getMapViewer().setCenterPosition(geoPosition);
        rootMapPanel.getMapViewer().setZoom(3);
    }

    public boolean isActive() {
        return active;
    }

    public void setActive(boolean active) {
        this.active = active;
    }

    private void setActiveNode(Layer layerNode) throws PropertyVetoException {
        for (Node layerListNode : layerListEM.getRootContext().getChildren().getNodes()) {
            if (((LayerListLayerNode) layerListNode).getLayer().equals(layerNode)) {
                layerListEM.setSelectedNodes(new Node[]{layerListNode});
                layerListPanel.addNotify();
            }
        }
    }

    @Override
    public Lookup getLookup() {
        return this.lookup;
    }

    @Override
    public ExplorerManager getExplorerManager() {
        return this.layerListEM;
    }

    @Override
    public void refreshLayer(Layer layer) {
        repaintLayer(layer);
    }

    private class NewLayerListener implements GuiEventListener<Layer> {

        @Override
        public void onEvent(GuiEvent<Layer> event) {
            try {
                Layer layer = event.getContent();
                if (layer instanceof ObjectLayer) {
                    LayerNode node = new LayerNode(layer,Children.create(new LayerNodeChildFactory((ObjectLayer)layer),false));
                    layerController.addLayerNode(node);
                } else {
                    
                    LayerNode node = new LayerNode(layer);
                    layerController.addLayerNode(node);
                }
            } catch (IntrospectionException ex) {
                Exceptions.printStackTrace(ex);
            }
        }

        @Override
        public boolean listensTo(GuiEventKey guiEventKey) {
            return LayerEventKey.NEW_LAYER.equals(guiEventKey);
        }

        @Override
        public String getModuleName() {
            return "geoviewer-jxmap";
        }
    }

    private final class MapLayerController implements LayerController {

        private final List<LayerNode> layerNodes = new ArrayList<LayerNode>();

        @Override
        public void addLayerNode(LayerNode layerNode) {
            this.layerNodes.add(layerNode);
            final Layer layer = layerNode.getLayer();
            addLookup(layer.getDataProvider().getLookup());
            //
            // register listener for pallete change
            //
            layer.addPropertyChangeListener(BaseLayer.PROPERTY_PALETTE, new PropertyChangeListener() {

                @Override
                public void propertyChange(PropertyChangeEvent evt) {
                    repaintLayer(layer);
                }
            });

            LayerPanel layerPanel = mapPanelFactory.createPanel(layerNode);

            layerPanelMap.put(layer, layerPanel);
            rootMapPanel.addLayerPanel(layerPanel);

            refreshLayerList();
        }

        @Override
        public void removeLayerNode(LayerNode layerNode) {
            this.layerNodes.remove(layerNode);
            
            removeLookup(layerNode.getLayer().getDataProvider().getLookup());
            
            rootMapPanel.removeLayerPanel(layerPanelMap.get(layerNode.getLayer()));
            layerPanelMap.remove(layerNode.getLayer());
            
            refreshLayerList();
        }

        @Override
        public void changeLayerInfo(LayerInfo layerInfo) {
            throw new UnsupportedOperationException("Not supported yet.");
        }

        @Override
        public Node[] getLayerNodes() {
            return this.layerNodes.toArray(new Node[this.layerNodes.size()]);
        }

        @Override
        public LayerInfo getLayerInfo(Layer layer) {
            assert layerPanelMap.containsKey(layer);
            return layerPanelMap.get(layer).getInfo();
        }
    }

    public class LayerDropTargetListener implements DropTargetListener {

        @Override
        public void dragEnter(DropTargetDragEvent dtde) {
        }

        @Override
        public void dragExit(DropTargetEvent dtde) {
        }

        @Override
        public void dragOver(DropTargetDragEvent dtde) {
        }

        @Override
        public void dropActionChanged(DropTargetDragEvent dtde) {
        }

        @Override
        public void drop(DropTargetDropEvent dtde) {
            Node node = NodeTransfer.node(dtde.getTransferable(), NodeTransfer.DND_COPY);
//            
            if (node != null) {
//            
//            if (dtde.isDataFlavorSupported(NodeTransfer.node(null, action)
//                LayerNode.DATA_FLAVOR)) {
                if (node instanceof LayerNode) {
                    dtde.acceptDrop(DnDConstants.ACTION_COPY);
                    LayerNode c = (LayerNode) node;
                    StatusDisplayer.getDefault().setStatusText(c.getName());
                    layerController.addLayerNode(c);
                }
            } else {
                dtde.rejectDrop();
                dtde.dropComplete(false);
            }
        }
    }
    
    public LayerController getLayerController(){
        return layerController;
    }
}
